#include "pch.h"
#include "CFWrapper.h"
#include "GaojieDriveApp.h"
#include "AppConfig.h"

#define CF_SIZEOF_OP_PARAM(field)	RTL_SIZEOF_THROUGH_FIELD(CF_OPERATION_PARAMETERS, field)

namespace CF
{

CCbWrapper::CCbWrapper(const CF_CALLBACK_INFO *pCallbackInfo)
	: m_connectionKey(pCallbackInfo->ConnectionKey)
	, m_transferKey(pCallbackInfo->TransferKey)
{
}

void CCbWrapper::ReportProgress(const std::wstring& strFileName, INT64 nTotal, INT64 nCompleted)
{
	AssertInThreadpool();

	LARGE_INTEGER liTotal, liCompleted;
	liTotal.QuadPart = nTotal;
	liCompleted.QuadPart = nCompleted;

	HRESULT hr = CfReportProviderProgress(m_connectionKey, m_transferKey, liTotal, liCompleted);
	assert(hr == S_OK);
	if (hr != S_OK)
		LogWarn(L"CfReportProgress error: %s, hr=0x%x", strFileName, hr);
}

void CCbWrapper::TransferData(const std::wstring& strFileName, const BufferPtr& pBuffer, INT64 nOffset, INT64 nLength)
{
	AssertInThreadpool();

	CF_OPERATION_INFO opInfo = { sizeof(opInfo), CF_OPERATION_TYPE_TRANSFER_DATA, m_connectionKey, m_transferKey };
	CF_OPERATION_PARAMETERS opParam = { CF_SIZEOF_OP_PARAM(TransferData) };
	opParam.TransferData.Buffer = pBuffer.get();
	opParam.TransferData.Offset.QuadPart = nOffset;
	opParam.TransferData.Length.QuadPart = nLength;

	if (!pBuffer)
		opParam.TransferData.CompletionStatus = STATUS_CLOUD_FILE_UNSUCCESSFUL;

	HRESULT hr = CfExecute(&opInfo, &opParam);
	assert(hr == S_OK);
	if (hr != S_OK)
		LogWarn(L"CfTransferData error: %s, hr=0x%x", strFileName, hr);
}

void GenPlaceholderArray(const std::vector<FileInfo>& vecFileInfos, std::vector<CF_PLACEHOLDER_CREATE_INFO>& vecPlaceholders)
{
	AssertInThreadpool();

	vecPlaceholders.reserve(vecFileInfos.size());

	for (const auto& fileInfo : vecFileInfos)
	{
		CF_PLACEHOLDER_CREATE_INFO placeholder = {};
		placeholder.RelativeFileName = fileInfo.strFileName.c_str();
		placeholder.FsMetadata.BasicInfo.LastWriteTime.LowPart = fileInfo.ftLastWrite.dwLowDateTime;
		placeholder.FsMetadata.BasicInfo.LastWriteTime.HighPart = fileInfo.ftLastWrite.dwHighDateTime;
		placeholder.FsMetadata.BasicInfo.FileAttributes = fileInfo.dwAttributes & 0xff;
		placeholder.FsMetadata.FileSize.QuadPart = fileInfo.nFileSize;
		placeholder.FileIdentity = "{}";
		placeholder.FileIdentityLength = 3;
		placeholder.Flags = CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC;

		if (fileInfo.dwAttributes & FILE_ATTRIBUTE_DIRECTORY)
			placeholder.FsMetadata.FileSize.QuadPart = 0;

		vecPlaceholders.emplace_back(placeholder);
	}
}

void CCbWrapper::TransferPlaceholders(const std::wstring& strPathName, bool bError, const std::vector<FileInfo>& vecFileInfos)
{
	AssertInThreadpool();

	CF_OPERATION_INFO opInfo = { sizeof(opInfo), CF_OPERATION_TYPE_TRANSFER_PLACEHOLDERS, m_connectionKey, m_transferKey };
	CF_OPERATION_PARAMETERS opParam = { CF_SIZEOF_OP_PARAM(TransferPlaceholders) };
	opParam.TransferPlaceholders.Flags = CF_OPERATION_TRANSFER_PLACEHOLDERS_FLAG_DISABLE_ON_DEMAND_POPULATION;

	std::vector<CF_PLACEHOLDER_CREATE_INFO> vecPlaceholders;

	if (bError)
	{
		opParam.TransferPlaceholders.CompletionStatus = STATUS_CLOUD_FILE_UNSUCCESSFUL;
	}
	else if (vecFileInfos.size())
	{
		GenPlaceholderArray(vecFileInfos, vecPlaceholders);
		opParam.TransferPlaceholders.PlaceholderTotalCount.QuadPart = vecPlaceholders.size();
		opParam.TransferPlaceholders.PlaceholderArray = vecPlaceholders.data();
		opParam.TransferPlaceholders.PlaceholderCount = (DWORD)vecPlaceholders.size();
	}

	HRESULT hr = CfExecute(&opInfo, &opParam);
	assert(hr == S_OK);
	if (hr != S_OK)
		LogWarn(L"CfTransferPlaceholders error: %s, hr=0x%x", strPathName, hr);
}

void CreatePlaceholders(const std::wstring& strPathName, const std::vector<FileInfo>& vecFileInfos)
{
	AssertInThreadpool();

	if (vecFileInfos.empty())
		return;

	std::vector<CF_PLACEHOLDER_CREATE_INFO> vecPlaceholders;
	GenPlaceholderArray(vecFileInfos, vecPlaceholders);

	HRESULT hr = CfCreatePlaceholders(strPathName.c_str(), vecPlaceholders.data(), (DWORD)vecPlaceholders.size(), CF_CREATE_FLAG_NONE, NULL);
	assert(hr == S_OK);
	if (hr != S_OK)
		LogError(L"CfCreatePlaceholders error: %s, hr=0x%x", strPathName, hr);
}

bool LocalEqualsRemote(const FIND_DATA& localInfo, const FileInfo& remoteInfo)
{
	// First compare the last write time, and then compare the size if it is a file
	if ((INT64&)localInfo.ftLastWriteTime != (INT64&)remoteInfo.ftLastWrite)
		return false;

	if (localInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		return true;

	LARGE_INTEGER liFileSize;
	liFileSize.LowPart = localInfo.nFileSizeLow;
	liFileSize.HighPart = localInfo.nFileSizeHigh;

	return liFileSize.QuadPart == remoteInfo.nFileSize;
}

// Convert to or update placeholder
void UpdatePlaceholder(const std::wstring& strPathName, const FIND_DATA& localInfo, const FileInfo& remoteInfo)
{
	AssertInThreadpool();

	std::wstring strFilePath = strPathName + localInfo.cFileName;
	bool bDirectory = (localInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
	CF_FS_METADATA metadata = {}, *pMetadata = nullptr;

	if (!LocalEqualsRemote(localInfo, remoteInfo))
	{
		pMetadata = &metadata;
		metadata.BasicInfo.LastWriteTime.LowPart = remoteInfo.ftLastWrite.dwLowDateTime;
		metadata.BasicInfo.LastWriteTime.HighPart = remoteInfo.ftLastWrite.dwHighDateTime;
		metadata.BasicInfo.FileAttributes = remoteInfo.dwAttributes & 0xff;
		metadata.FileSize.QuadPart = remoteInfo.nFileSize;

		if (bDirectory)
			metadata.FileSize.QuadPart = 0;

		LogWarn(L"Update metadata: %s, bDirectory=%d", strFilePath, bDirectory);
	}

	auto phState = CfGetPlaceholderStateFromFindData(&localInfo);
	assert(phState != CF_PLACEHOLDER_STATE_INVALID);
	if (phState == CF_PLACEHOLDER_STATE_INVALID)
		LogWarn(L"CfGetPlaceholderState error: %s", strFilePath);

	// If it's a placeholder and don't need to update the metadata
	bool bPlaceholder = (phState & CF_PLACEHOLDER_STATE_PLACEHOLDER) != 0;
	if (bPlaceholder && !pMetadata && (!bDirectory || (phState & CF_PLACEHOLDER_STATE_PARTIAL)))
		return;

	CF_OPEN_FILE_FLAGS openFlags = CF_OPEN_FILE_FLAG_NONE;
	if (!bDirectory)
		openFlags |= CF_OPEN_FILE_FLAG_EXCLUSIVE;

	HANDLE hFile;
	HRESULT hr = CfOpenFileWithOplock(strFilePath.c_str(), openFlags, &hFile);
	assert(hr == S_OK);
	if (hr != S_OK)
	{
		LogError(L"CfOpenFileWithOplock error: %s, hr=0x%x", strFilePath, hr);
		return;
	}

	if (!bPlaceholder)
	{
		CF_CONVERT_FLAGS convertFlags = CF_CONVERT_FLAG_MARK_IN_SYNC;
		if (bDirectory)
			convertFlags |= CF_CONVERT_FLAG_ENABLE_ON_DEMAND_POPULATION;

		hr = CfConvertToPlaceholder(hFile, "{}", 3, convertFlags, NULL, NULL);
		assert(hr == S_OK);
		if (hr != S_OK)
			LogError(L"CfConvertToPlaceholder error: %s, hr=0x%x", strFilePath, hr);
	}

	if (bPlaceholder || pMetadata)
	{
		CF_UPDATE_FLAGS updateFlags = CF_UPDATE_FLAG_MARK_IN_SYNC;
		if (bDirectory)
			updateFlags |= CF_UPDATE_FLAG_ENABLE_ON_DEMAND_POPULATION;
		else if (pMetadata)
			updateFlags |= CF_UPDATE_FLAG_DEHYDRATE;

		hr = CfUpdatePlaceholder(hFile, pMetadata, NULL, 0, NULL, 0, updateFlags, NULL, NULL);
		assert(hr == S_OK);
		if (hr != S_OK)
			LogError(L"CfUpdatePlaceholder error: %s, hr=0x%x", strFilePath, hr);
	}

	CfCloseHandle(hFile);
}

std::wstring GetRecyclePath()
{
	return GetApp()->GetAppConfig()->GetClientRoot() + L".GaojieDriveRecycle\\";
}

std::wstring GetRecycleTempPath()
{
	std::wstring strTempPath = GetRecyclePath();
	const size_t nPathLen = strTempPath.size();

	FILETIME ftRandom;
	GetSystemTimeAsFileTime(&ftRandom);

	for (int i = 0; i != 10; i++)
	{
		strTempPath += std::to_wstring(++(INT64&)ftRandom);

		if (CreateDirectoryW(strTempPath.c_str(), NULL))
		{
			strTempPath += '\\';
			break;
		}

		DWORD dwError = GetLastError();
		strTempPath.resize(nPathLen);

		if (dwError == ERROR_PATH_NOT_FOUND)
		{
			CreateDirectoryW(strTempPath.c_str(), NULL);
			SetFileAttributesW(strTempPath.c_str(), FILE_ATTRIBUTE_HIDDEN);

			HANDLE hFile;
			HRESULT hr = CfOpenFileWithOplock(strTempPath.c_str(), CF_OPEN_FILE_FLAG_NONE, &hFile);
			assert(hr == S_OK);
			if (hr == S_OK)
			{
				hr = CfSetPinState(hFile, CF_PIN_STATE_EXCLUDED, CF_SET_PIN_FLAG_NONE, NULL);
				assert(hr == S_OK);
				CfCloseHandle(hFile);
			}
		}
	}

	return strTempPath;
}

}	// namespace CF
